/* ******************************************************** */
/* * Monte Carlo Method for finding PI - Parallel Version * */
/* ******************************************************** */

#include <limits.h>
#include <math.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "../include/rand.h"

/* Default values */
#define DEFAULT_ATTEMPTS 1000000000
#define DEFAULT_THREADS 4

/* Try function: Randomly place a dott inside a square/circle with diameter/length of 1 unit. Return true if dot is inside the circle */
bool try(unsigned int seed) {
	double x, y;

	/* Randomly pick x coordinate */
	unsigned int randVal = randNext(seed) % UINT_MAX;
	x = (double) randVal / UINT_MAX;

	/* Randomly pick y coordinate */
	randVal = randNext(randVal) % UINT_MAX;
	y = (double) randVal / UINT_MAX;

	/* Return true if dot is inside the circle */
	return ((pow(x,2) + pow(y,2)) <= 1.0)? 1 : 0;
}

/* Run many iterations of try(). Thread function */
void *iterateTry(void *voidIters) {
	int i, *hits, *iters = (int *) voidIters, errorNumber = 0;
	time_t timeSeed = time(NULL);

	/* This variable will be free'd after read in main thread */
	hits = malloc(sizeof(int));

	/* Run try() sequentially */
	for(i = 0; i < *iters; i++)
		*hits += try(i*timeSeed);

	/* Free received value from main thread */
	free(voidIters);

	/* Return hits value and exit thread */
	pthread_exit(hits);
}

int main(int argc, char *argv[]) {
	int i, totalHits = 0, attempts, numThreads, segmentSize, segmentSizeRemain, errorNumber = 0;
	double pi;
	pthread_t *thread;

	/* No arguments are passed, default values are used */
	if(1 == argc) {
		attempts = DEFAULT_ATTEMPTS;
		numThreads = DEFAULT_THREADS;
	}
	/* Arguments are passed and used */
	else if(3 == argc) {
		attempts = atoi(argv[1]);
		numThreads = atoi(argv[2]);
	}
	/* Invalid number of arguments */
	else {
		printf("\tUsage: %s [numIters numThreads]\n\tWhere:\n\t\t numIters: number of iterations (default by omission: %d)\n\t\t numThreads: number of iterations (default by omission: %d)\n", argv[0], DEFAULT_ATTEMPTS, DEFAULT_THREADS);
		exit(1);
	}

	/* Malloc threads list */
	thread = malloc(numThreads*sizeof(pthread_t));

	/* Calculate how many iterations each thread will run */
	segmentSize = attempts / numThreads;
	segmentSizeRemain = attempts % numThreads;

	for(i = 0; i < numThreads; i++) {
		/* Define how many iteration this thread will run. If this is last thread to be created, run all remaining iterations */
		int *numIters = malloc(sizeof(int));
		*numIters = (numThreads-1 == i)? segmentSize + segmentSizeRemain : segmentSize;

		/* Create thread */
		if((errorNumber = pthread_create(&thread[i], NULL, iterateTry, (void *) numIters)) != 0) {
			fprintf(stderr, "%s: pthread_create failed with error code %d.\n", argv[0], errorNumber);
			exit(1);
		}
	}

	/* Join all threads and get their respectives return values */
	for(i = 0; i < numThreads; i++) {
		int *retVal;

		if((errorNumber = pthread_join(thread[i], (void **) &retVal)) != 0) {
			fprintf(stderr, "%s: pthread_join failed with error code %d.\n", argv[0], errorNumber);
			exit(1);
		}

		/* Add thread hits to total hits */
		totalHits += *retVal;

		/* Free returned value (was malloc'd inside the thread) */
		free(retVal);
	}

	/* Calculate PI using Monte Carlo */
	pi = (4.0 * totalHits) / attempts;
	printf("PI=%.6lf\n", pi);

	/* Free threads list */
	free(thread);

	return 0;
}	
